package dk.au.cs.peterbn.patterns.impl;

import dk.au.cs.peterbn.patterns.DesignPattern;
import dk.au.cs.peterbn.patterns.MemberRole;
import dk.au.cs.peterbn.patterns.TypeRelation;
import dk.au.cs.peterbn.patterns.TypeRole;

import java.util.*;

/**
 * Implementation of TypeRole interface
 */
public class TypeRoleImpl implements TypeRole, HasModifiableDescription {
    private final DesignPatternImpl designPattern;
    private final String name;
    private Target target;
    private String description;
    private List<RelationProto> relationProtos;
    private Map<String,MemberRoleImpl> members;

    TypeRoleImpl(DesignPatternImpl designPattern, String name, Target target) {
        this.designPattern = designPattern;
        this.name = name;
        this.target = target;
        relationProtos = new ArrayList<RelationProto>();
        members = new HashMap<String, MemberRoleImpl>();
        this.description = "";
    }

    @Override
    public Target getTarget() {
        return target;
    }

    @Override
    public MemberRole getMember(String role) {
        return getMembers().get(role);
    }

    @Override
    public Set<TypeRole> getImplementers() {
        Set<TypeRole> implementers = new HashSet<TypeRole>();
        for (TypeRole typeRole : getPattern().getTypes()) {
            Collection<? extends TypeRelation> relations = typeRole.getRelations();
            for (TypeRelation relation : relations) {
                if (relation.getKind() == TypeRelation.Kind.IS_A) {
                    if (relation.getTarget().equals(this)) {
                        implementers.add(typeRole);
                    }
                }
            }
        }
        return implementers;
    }

    @Override
    public DesignPattern getPattern() {
        return designPattern;
    }

    @Override
    public Map<String, MemberRole> getMembers() {
        Map<String, MemberRole> allMembers = new HashMap<String, MemberRole>(members);
        for (TypeRelation relation : getRelations()) {
            if (relation.getKind() == TypeRelation.Kind.IS_A) {
                allMembers.putAll(relation.getTarget().getMembers());
            }
        }
        return allMembers;
    }

    @Override
    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public Collection<TypeRelation> getRelations() {
        List<TypeRelation> relations = new ArrayList<TypeRelation>();
        for (RelationProto relationProto : relationProtos) {
            TypeRole typeRole = designPattern.getTypeRole(relationProto.getTarget());
            if (typeRole != null) {
                relations.add(new TypeRelationImpl(relationProto.getKind(), typeRole));
            } else {
                //This hopefully won't happen, as the target is an IDREF, but someone might just reference a method descriptor for the fun of it.
                throw new InvalidPatternDescriptionException("Unresolved role reference from " + getName() + " to " + relationProto.getTarget());
            }
        }
        return relations;
    }

    void addRelationProto(RelationProto relationProto) {
        relationProtos.add(relationProto);
    }

    public String getName() {
        return name;
    }

    @Override
    public Collection<TypeRole> getParentRoles() {
        Collection<TypeRole> parentRoles = new HashSet<TypeRole>();
        for (TypeRelation relation : getRelations()) {
            if (relation.getKind() == TypeRelation.Kind.IS_A) {
                parentRoles.add(relation.getTarget());
            }
        }
        return parentRoles;
    }

    @SuppressWarnings({"RedundantIfStatement"})
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        TypeRoleImpl typeRole = (TypeRoleImpl) o;

        if (!designPattern.equals(typeRole.designPattern)) return false;
        if (!name.equals(typeRole.name)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = designPattern.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }

    void addMember(MemberRoleImpl memberRole) {
        members.put(memberRole.getName(), memberRole);
    }

    /**
     * Get a member that was declared in this type, without any inheritance resolving.
     * @param name The MemberRoleImpl name to look up
     * @return The member role, if declared
     */
    MemberRoleImpl getOwnMember(String name) {
        return members.get(name);
    }

    /**
     * Clears the internal member map and returns the old contents.
     * @return The old member map
     */
    Map<String, MemberRoleImpl> getAndClearOwnMembers() {
        Map<String, MemberRoleImpl> tmp = members;
        members = new HashMap<String, MemberRoleImpl>();
        return tmp;
    }

    void clearRelationProtos() {
        relationProtos.clear();
    }

    void setTarget(Target target) {
        this.target = target;
    }
}
